/**
* @file	TheSOSS.cpp
* @author  Thomas Fu <thomas.ks.fu@gmail.com>
* @version 0.0.1
* @date	04/28/2011	
*
* @section License
* This program is propety of The SOSS and may not be used by any 
* individual for any purpose at any time without express permission from 
* Mike Casey, Thomas Fu, Chris Hairfield, Kyle Lamson, Ben Treweek, or Cliff 
* Warren.
*
* @section Arguments
* ./TheSoss N fs <iDevice> <oDevice> <iChannelOffset> <oChannelOffset> \n
* N = number of channels \n
* fs = sample rate (samples per second)\n
* iDevice = (0 is microphone, 1 is line-in)\n
* oDevice = (2 is speakers, not sure what the other two are)\n
* iChannelOffset = input channel offset (default = 0)\n
* oChannelOffset = output channel offset (default = 0)\n
*
* @brief 
* This file does real-time processing of audio signals on a Macbook Pro. In its
* current configuration, it reads a single-channel signal from the line-in
* port, performs some processing, and plays the modified signal over the 
* computer's speakers.
*/

#include <cmath>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <map>

#include "Audio.h"
#include "AudioDevice.h"
#include "Debugging.h"
#include "Distortion.h"
#include "FastNN.h"
#include "IIRFilter.h"
#include "IODataTypes.h"
#include "NeuralNet.h"
#include "TubeScreamer.h"
#include "RtAudio.h"
#include "xmlwriter.h"

#include <Core>
#include <Dense>
#include <Eigenvalues>

typedef signed short MY_TYPE;

//define FORMAT RTAUDIO_SINT16
#define SCALE  32767.0

// Sleep command specific to Windows
#define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) 

//	Namespace for Linear Algebra functions from the Eigen Library
using namespace Eigen;

//	Standard namespace
using namespace std;

/** Constant to specify format for Real Time Audio data */
#define FORMAT RTAUDIO_FLOAT64

// Reads a matrix of unknown dimensions in from a file
int readMatrixFromFileNN(double** & A, int & m, int & n, string filename)
{
  ifstream infile;
  infile.open(filename.c_str());
  
  // If the file cannot be opened
  if(infile.fail())
	{
		cout << "Invalid file name." << endl;
		return -1;
	}
  
  // From cplusplus.com on ifstream
  infile.seekg(0, ios::end);
  int length = infile.tellg();
  infile.seekg(0, ios::beg);
  
  // Figure out the number of rows of the matrix if not known
  if(m == 0){
    char* buffer = new (nothrow) char[length];
    infile.read (buffer,length);
    for(int i = 0; i < length; i++)
		{
			if(buffer[i] == '\n')
				m++;
		}
    infile.seekg(0, ios::beg);
    delete[] buffer;
  }
  
  string line;
  // Determine number of columns of the matrix if not known
  if(n == 0)
	{
		getline(infile, line);
		
		for(unsigned int i = 0; i < line.size(); i++)
		{
			if(line[i] == '\t')
				n++;
		}
		infile.seekg(0, ios::beg);
	}
	
	m++;
	n++;
  
	//cout << m << endl;
	//cout << n << endl;
	
  // Create A as an array of double*'s
  A = new (nothrow) double*[m];
  if(A == NULL)
	{
		cout << "Error: Memory allocation error";
		exit(-1);
	}
  for(int i = 0; i < m; i++)
	{
		// Allocate space for each row of A
		A[i] = new (nothrow) double[n];
		if(A[i] == NULL)
		{
			cout << "Error: Memory allocation error";
			exit(-1);
		}
		
		// Read in a single row
		getline(infile, line);
		
		// Convert the string line to a non-const c-string for use with strtok
		char* cLine = new (nothrow) char[line.size()+1];
		if(cLine == NULL)
		{
			cout << "Error: Memory allocation error";
			exit(-1);
		}
		
		memcpy(cLine, line.c_str(), (line.size() + 1) * sizeof(char));
		
		// Parse each element of the row
		char* pch = strtok(cLine, "\t");
		for(int j = 0; j < n; j++)
		{
			A[i][j] = atof(pch);
			pch = strtok(NULL, "\t");
		}
		delete[] cLine;
	}
  
  // Successful execution
  return 0;
}


/**
* The main function responsible for execution of this program. Eventually this 
* will do more than just run a single, constant effect from input to output.
*
* @param argc A count of the number of arguments entered at the command line 
* when this program is executed
*
* @param argv An array of char*'s that contains the list of arguments passed 
* into this program when it is executed
*
* @return An integer encoding information about the successful execution of 
* this function. A zero indicates successful execution with no problems.
*/
int main(int argc, char *argv[])
{
	int choice = 0;
	int gain = 50, tone = 50, volume = 50, dirty = 50;
	unsigned int oDevice = DEFAULT_OUTPUT_DEVICE; 
	unsigned int iDevice = DEFAULT_INPUT_DEVICE; 

	// Find audio devices
	int defaultInput, defaultOutput;
	vector<audioDevice> *devices;
	devices = audioProbe(*(getADAC()), defaultInput, defaultOutput);
	setDevices(devices);

	//RtAudio *rt = getADAC();
	oDevice = (*(getADAC())).getDefaultOutputDevice();
	
/*	double **data;
	int rows = 0;
	int cols = 0;
	//cout << "HERE" << endl;
	if(readMatrixFromFileNN(data, rows, cols, argv[1]) == -1)
	{
		cout << "Error reading file " << argv[1] << endl;
		return -1;
	}
	//cout << "here" << endl;
	//cols++;
	NeuralNet<double> nn (cols - 1, (cols-1)*2, (cols-1)*2, 1);
	
	
	double** input = new double*[rows];
	for(int i = 0; i < rows; i++)
	{
		input[i] = new double[cols-1];
		for(int j = 0; j < cols-1; j++)
			input[i][j] = data[i][j];
	}
	
	double** output = new double*[rows];
	for(int i = 0; i < rows; i++)
	{
		output[i] = new double[1];
		output[i][0] = data[i][cols-1];
	}
	*/
	//int turns = 100001;
	
	//nn.trainNetwork(input, output, rows, turns);
	/*nn.writeToFile();
	ofstream outfile;
	outfile.open("output.txt");
	cout << endl << endl;
	cout << "Results" << endl;
	cout << "Cols: " << cols << endl;
	for(int j = 0; j < cols-1; j++)
		cout << "Input " << j << "\t";
	cout << "Network" << "\t" << "Actual" << endl;
	double* inputP = new double[cols-1];
	for(int i = 0; i < rows; i++)
	{
		for(int j = 0; j < cols - 1; j++)
			inputP[j] = data[i][j];
		double* results = nn.predict(inputP);
		for(int j = 0; j < cols-1; j++)
		{
			outfile << input[i][j] << "\t";
			cout << input[i][j] << "\t";
		}
		outfile << results[0] << "\t" << output[i][0] << endl;
		cout << results[0] << "\t" << output[i][0] << endl;
		delete results;
	}
*/
	
	//xmlwriter writer("NN.xml");
	//nn.writeToFile();
	//writer.CloseAlltags()
	
	// Run main control loop
	while(true){
		choice = printMainMenu();
		switch(choice)
		{
		case 1: printDistortionParameters(gain, tone, volume, dirty); break;
		case 2: setDistortionParameters(gain, tone, volume, dirty); break;
		case 3: listAudioDevices(*devices, iDevice, oDevice); break;
		case 5: playWaveFile(*(getADAC()), iDevice, oDevice, gain, tone, volume, dirty, *(getDevices())); break;
		case 6: playLiveStream(*(getADAC()), iDevice, oDevice, gain, tone, volume, dirty, *devices); break;
		case 7: exit(1); break;
		}
	}

	return 0;
}
